#!/usr/bin/env python
# -*- coding: utf-8 -*-
'''manage upload task using threading pool'''

import threading
import time
import os
import sys
import datetime

from .utility.pathutils import formatbytes
from .utility.gadget import get_disk_queue

BYTE_CONFIGS = {  'forcekb' : False,
            'largestonly' : True,
            'kiloname' : 'KB',
            'meganame' : 'MB',
            'bytename' : 'B',
            'nospace' : False}

SPEED_CONFIGS = {  'forcekb' : False,
            'largestonly' : True,
            'kiloname' : 'KB/s',
            'meganame' : 'MB/s',
            'bytename' : 'B/s',
            'nospace' : False}

class UserStop(Exception):
    pass

class UnknownNonResponse(Exception):
    pass
    
class ProgressLog(object):
    def __init__(self, task):
        self.task = task
        self.start_time = None    
                
    def _write(self, download_t, download_d, upload_t, upload_d, current_seek_point=-1):
        if current_seek_point > 0:
            if self.task.subtask_seek_end and current_seek_point > self.task.subtask_seek_end+1:
                return -1

        current = int(upload_d)
        total = int(upload_t)
        if self.task.status == UploadTask.STOPED:
            raise UserStop("Task(%s,%s) was Stop by user." % (self.task.file, self.task.host.hostname))
        if self.start_time is None:
            self.start_time = datetime.datetime.now()
            return
        current_time = datetime.datetime.now()
        delta_time = current_time - self.start_time
        delta_seconds = delta_time.seconds + delta_time.microseconds/1000000 + 1 # plus 1 to avoid divide by zero error
        
        if current == 0 or total == 0:
            return
        self.task.progress = str(100*current/total) + '%'
        self.task.speed = formatbytes(current/delta_seconds, configdict=SPEED_CONFIGS)
        self.task.size = formatbytes(current, configdict=BYTE_CONFIGS) + \
                        "/" + formatbytes(total, configdict=BYTE_CONFIGS)
        if current == total:
            self.task.sent_all_data = True
            
class CurlProgressLog(ProgressLog):
    def __init__(self, task):
        ProgressLog.__init__(self, task)
    
    def write(self, download_t, download_d, upload_t, upload_d):
        return self._write(download_t, download_d, upload_t, upload_d)
            
class PosterProgressLog(ProgressLog):
    def __init__(self, task):
        ProgressLog.__init__(self, task)
    
    def write(self, mparam, upload_t, upload_d, thisblocksize=-1):
        return self._write(0, 0, upload_t, upload_d, thisblocksize)

class TaskData(object):
    def __init__(self, task):
        self.infos = task.show()
        self.infos = list(self.infos)  
        self.infos.append(task.result)
        
    def recoverTask(self, host, pool):
        #return (filepath[1], filepath[0], self.host.hostname, self.targetDir, self.status, self.size, self.progress, self.speed, self.link)
        file = os.path.join(self.infos[1],self.infos[0])
        task = UploadTask(file, host, pool)#file, host, pool
        task.status = self.infos[4]
        if task.status == UploadTask.UPLOADIND or \
            task.status == UploadTask.START:
            task.status = UploadTask.STOPED
        task.targetDir = self.infos[3]
        task.size = self.infos[5]
        task.progress = self.infos[6]
        task.speed = self.infos[7]
        task.link = self.infos[8]
        task.result = self.infos[-1]
        return task
    
    def getHostname(self):
        return self.infos[2]
        
class UploadTask(object):
    
    #task status
    
    EXISED = 'Existed'      # file has been in host 
    FINISHED = 'Finished'   # upload done
    
    READY = 'Ready'         # new added
    START = 'Start'         # Start by SetStart function, and add to upload queue
    
    UPLOADIND = 'Uploading' # uploading    
    FAILED = 'Failed'       # fail to upload
    STOPED = 'Stoped'       # stop by user
    
    def __init__(self, file, host, pool=None, other_args={}, retry=3, file_range=None):
        self.retry = retry
        self.sent_all_data = False
        self.pool = pool
        self.host = host
        self.file = file 
        self.file_range = file_range
        self.subtask_seek_end = None
        if self.file_range is not None:
            self.subtask_seek_end = self.file_range[1]
        #make sure we pass a unicode path to Http-Post function
#        if not isinstance(self.file, unicode):
#            self.file = unicode(self.file, sys.getfilesystemencoding())
        self.status = self.READY
        self.result = None
        self.targetDir = ''
        self.size = ''
        self.progress = ''
        self.speed = ''
        self.link = ''
        self.stopSignal = False
        self.other_args = {}
        if other_args:
            self.other_args = other_args.copy()
        
    def show(self):
        '''
        return a tuple for listctrl to show
        (file, host, status, progress, speed, link)
        '''
        filepath = os.path.split(self.file)
        return (filepath[1], filepath[0], self.host.hostname, self.targetDir, self.status, self.size, self.progress, self.speed, self.link)
            
    def run(self):
        '''
        call upload function of host instance
        then save upload result to member var
        '''
        self.status = self.UPLOADIND
        self.pool.notifyChange(self)
        self.host.loginLock.acquire()
        isLogin = False
        try:
            isLogin = self.host.login()
        except:
            pass
        self.host.loginLock.release()
        if isLogin != True:
            self.host.fherr.write("Fail to Login" + self.host.hostname) 
            self.status = self.FAILED
        else:
            uploadThreading = threading.Thread(name="[Upload_inner]"+self.file, target=self.runUpload)
            uploadThreading.setDaemon(True)
            uploadThreading.start()
            while (not self.stopSignal and \
                   uploadThreading.isAlive() and \
                   not self.sent_all_data):
                self.pool.notifyChange(self)
                time.sleep(2)
            
            #in order to escape from an unknown no-response error
            #we set 10s to wait for result, after all data have sent
            wait_time = self.host.wait_time
            while (self.sent_all_data and wait_time > 0\
                   and self.result is None\
                   and not self.stopSignal \
                   and uploadThreading.isAlive()):
                sys.stderr.write("All data sent, wait for result, %s\r" % wait_time)
                time.sleep(1)
                wait_time -= 1
                
            if self.result is not None:
                self.status = self.FINISHED
                self.link = self.result.link
                self.progress = '100%'
                self.pool.notifyFinish(self)
            elif not self.stopSignal:
                #if stopSignal is True, the status has been change, no need to deal with it here
                self.status = self.FAILED
                #has a retry
                #FIXME: how to deal with the ghost threading? file has to close!!!
                if self.retry > 0:
                    self.host.fherr.write("Failed, have a retry for %s" % self.file)
                    self.retry -= 1
                    self.pool.setStart([self.pool.tasks.index(self),])
                    return
#                    raise UnknownNonResponse
            else:
                self.status = self.STOPED
        self.pool.notifyChange(self)
    
    def get_log_instance(self):
        '''
        now we have to way to post file.
        so have two class of progresslog
        '''
        if self.host.logtype == 'curl':        
            return CurlProgressLog(self)
        elif self.host.logtype == 'poster':
            return PosterProgressLog(self)

    def runUpload(self):
        if self.file_range is not None:
            self.result = self.host.upload_web(self.file, 
                                           progressLog=self.get_log_instance(), 
                                           other_args=self.other_args,
                                           send_range=self.file_range)
        else:
            self.result = self.host.upload_web(self.file, 
                                           progressLog=self.get_log_instance(), 
                                           other_args=self.other_args)
        
    def SetStopSignal(self, value):
        '''
        notify function "run" to exit.
        '''
        self.stopSignal = value
        
        
class TaskPool(threading.Thread):
    def __init__(self, maxRunningTask=8):
        threading.Thread.__init__(self)
        self.tasks = [] #save and manage tasks in a pool
        self.runningTask = []
        self.startTask = []
        self.maxRunningTask = maxRunningTask
        self.foreverFlag = False
        self.changelisteners = []
        self.finishlisteners = []
        
    def addChangeListener(self, listenerFunc):
        self.changelisteners.append(listenerFunc)
        
    def addFinishListener(self, listenerFunc):
        self.finishlisteners.append(listenerFunc)
        
    def OnChange(self, id):
        for listener in self.changelisteners:
            listener(id)
    def OnFinish(self, id):
        for listener in self.finishlisteners:
            listener(id)
            
    def notifyChange(self, task):
        '''
        notify the registed display that task data has been changed
        '''
        id = self.tasks.index(task)
        self.OnChange(id)  
        
    def notifyFinish(self, task):      
        '''
        notify the registed display that task has been finished
        '''
        id = self.tasks.index(task)
        self.OnFinish(id) 
        
    def addTask(self, task):
        '''
        add a task to pool
        '''
        self.tasks.append(task)
    
    def delTask(self, id):
        '''
        del a task 
        but task that is running or started should be stopped at first
        '''
        if self.tasks[id].status != UploadTask.UPLOADIND or \
            self.tasks[id].status != UploadTask.START:
            self.tasks.pop(id)
        
    def setStart(self, ids):
        '''
        Reset fail-task to reupload
        '''
        for id in ids:
            task = self.tasks[id]
            if task.status == UploadTask.FINISHED or \
                task.status == UploadTask.EXISED or \
                task.status == UploadTask.UPLOADIND :
                continue
            if task.status == UploadTask.FAILED or task.status == UploadTask.STOPED:
                #rebulit a new threading to restart the failed/stoped task
                task = UploadTask(task.file, task.host, self, \
                                  other_args=task.other_args, \
                                  retry=task.retry,
                                  file_range=task.file_range)
                task.status = UploadTask.START
                self.tasks[id] = task                
                self.notifyChange(task)
            if task.status == UploadTask.READY:
                task.status = UploadTask.START
            if task in self.startTask:
                continue
            self.startTask.append(task)
            self.OnChange(id)
    
    def stopOneTask(self, id):
        task = self.tasks[id]
        #if it is uploading, send a signal
        if task.status ==  UploadTask.UPLOADIND:
            task.SetStopSignal(True)
        #if it hasn't run, remove from startlist, and change status
        if task.status == UploadTask.START:
            self.startTask.remove(task)
            task.status = UploadTask.STOPED
            self.notifyChange(task)
            
    def stopTask(self, ids):
        '''
        stop selected tasks
        '''
        for id in ids:
            self.stopOneTask(id)
                
    def stopAllTask(self):
        '''
        stop all running task
        '''
        for id in range(len(self.tasks)):
            self.stopOneTask(id)
    
    def setForever(self, flag=False):
        '''
        set a flag to tell taskmanager to run forever or exit when all task done.
        '''
        self.foreverFlag = flag
    
    def run(self):
        '''
        override fucntion "run"
        start to upload task list
        '''
        self.runningTask = []     
        aliveCount = 0   
        while (self.foreverFlag or \
               len(self.startTask) > 0 or \
               len(self.runningTask)):
            aliveCount = 0
            #check how many threads is running
            for t in self.runningTask:
                if t.isAlive():
                    aliveCount += 1
                else:
                    self.runningTask.remove(t) 
            if aliveCount < self.maxRunningTask and len(self.startTask) > 0:
                newTask = self.startTask[0]
                filepath = newTask.file
                if get_disk_queue(filepath) <= 5:
                    newTask = self.startTask.pop(0)
                    newthread = threading.Thread(target=newTask.run)
                    self.runningTask.append(newthread) 
                    newthread.setDaemon(True)
                    newthread.start()
                else:
                    sys.stderr.write('Disk Queue Length > 5, wait 5mins.\n')  
                    time.sleep(300)
            time.sleep(3)
        #wait for the running task to exit
        for s in self.startTask:
            sys.stderr.write('you miss: %s' % s.file)
        for rt in self.runningTask:
            rt.join()
    
    
    
        
